Skip to content

feat (icons): Icons API end points for retrieving icons.#209

Open
dlabaj wants to merge 13 commits intomainfrom
icons-api
Open

feat (icons): Icons API end points for retrieving icons.#209
dlabaj wants to merge 13 commits intomainfrom
icons-api

Conversation

@dlabaj
Copy link
Contributor

@dlabaj dlabaj commented Feb 4, 2026

Closes issue #173 by adding support for PF Icons.

Summary by CodeRabbit

  • New Features

    • Added API endpoints to list available icons with optional case-insensitive name filtering.
    • Added API endpoint to retrieve individual icon SVGs.
  • Dependencies

    • Updated PatternFly React icons package.
    • Added react-icons support.
  • Configuration

    • Updated Cloudflare adapter image service configuration for improved image handling.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Feb 4, 2026

Deploying patternfly-doc-core with  Cloudflare Pages  Cloudflare Pages

Latest commit: ce4583d
Status: ✅  Deploy successful!
Preview URL: https://12cb3831.patternfly-doc-core.pages.dev
Branch Preview URL: https://icons-api.patternfly-doc-core.pages.dev

View logs

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 4, 2026

Walkthrough

Introduces a new PatternFly icons API with GET endpoints for listing and retrieving SVGs, including utility modules for icon metadata management and fetching. Adds comprehensive test coverage, updates OpenAPI documentation, modifies Astro configuration for image compilation, and refreshes dependencies.

Changes

Cohort / File(s) Summary
Configuration & Dependencies
astro.config.mjs, package.json, wrangler.jsonc
Updated Astro adapter with imageService compile option, added react-icons and updated PatternFly dependencies, and advanced Cloudflare compatibility date.
Icon Utility Modules
src/utils/icons/icons.ts, src/utils/icons/reactIcons.ts, src/utils/icons/fetch.ts, src/utils/apiHelpers.ts, src/utils/apiIndex/get.ts
Created utilities for icon metadata (IconMetadata interface), kebab-to-React name conversion, SVG retrieval by set and icon name, filtering, icons index fetching, and SVG response creation. Added getVersionsFromIndexFile utility.
API Routes
src/pages/api/[version]/icons/[iconName].ts, src/pages/api/[version]/icons/index.ts, src/pages/api/[version]/icons/[iconSet].ts, src/pages/iconsIndex.json.ts
Implemented four new API routes: individual icon SVG retrieval, icons metadata listing with filtering, icons set SVG mapping, and prerendered icons index JSON. All include parameter validation and comprehensive error handling.
API Documentation
src/pages/api/index.ts, src/pages/api/openapi.json.ts
Added documentation for two new icons endpoints (list with optional filter and single icon retrieval) with request/response schemas and OpenAPI specifications.
Test Suite
src/__tests__/pages/api/__tests__/[version]/icons/[iconName].test.ts, src/__tests__/pages/api/__tests__/[version]/icons/index.test.ts
Added comprehensive tests covering successful responses, parameter validation (400), nonexistent resources (404), and fetch failures (500) for both icon retrieval and listing endpoints.

Sequence Diagram

sequenceDiagram
    participant Client
    participant API as API Route<br/>([iconName].ts)
    participant APIIndex as API Index
    participant IconsIndex as Icons Index
    participant SVGs as Icon SVGs

    Client->>API: GET /api/v1/icons/angle-right
    API->>APIIndex: fetchApiIndex(url)
    APIIndex-->>API: { versions: [...] }
    API->>IconsIndex: fetchIconsIndex(url)
    IconsIndex-->>API: [{ name, reactName, usage }, ...]
    API->>SVGs: fetchIconSvgs(url, version, 'pf')
    SVGs-->>API: { reactIconName: '<svg>...</svg>', ... }
    API->>API: Locate icon by reactName
    API-->>Client: SVG Content (image/svg+xml)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • Add API support for icons #173: Directly implements the icon API endpoints (GET /api/{version}/icons and GET /api/{version}/icons/{icon-name}) and supporting utilities described in the issue.

Possibly related PRs

Suggested labels

released

Suggested reviewers

  • thatblindgeye
  • kmcfaul
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.68% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main feature: adding Icons API endpoints for retrieving PatternFly icons, which matches the primary focus of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch icons-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/__tests__/pages/api/__tests__/`[version]/icons/index.test.ts:
- Line 100: The test's assertion calls expect(body.icons.every((i: { name:
string }) => i.name.includes('circle'))) but never asserts a value, so it always
passes; update the assertion to actually check the boolean (e.g., call
.toBe(true) or .toBeTruthy()) on the expression using body.icons and the
existing predicate to ensure the test fails when no icon names include "circle".
🧹 Nitpick comments (9)
src/utils/icons/reactIcons.ts (3)

50-54: Static analysis ReDoS warning is a false positive in this context.

The regex at line 51 is constructed from setId values that originate from IconsManifest (react-icons library internal data), not from user input. The pattern ^${setPrefix} is a simple anchored prefix match without problematic quantifiers, making ReDoS practically impossible here.

However, if you want to be extra defensive, you could sanitize the input or use a literal string match instead:

♻️ Optional: Use string operations instead of regex
   } else {
     const setPrefix = setId.charAt(0).toUpperCase() + setId.slice(1)
-    const prefix = new RegExp(`^${setPrefix}`, 'i')
-    base = base.replace(prefix, '')
+    if (base.toLowerCase().startsWith(setPrefix.toLowerCase())) {
+      base = base.slice(setPrefix.length)
+    }
   }

82-84: Consider logging failed icon set loads for debugging.

Silent failures make troubleshooting difficult. While graceful degradation is appropriate, logging would help identify issues during development or when icon sets are unexpectedly unavailable.

♻️ Optional: Add debug logging
-    } catch {
-      // Skip sets that fail to load
+    } catch (error) {
+      // Skip sets that fail to load, but log for debugging
+      console.debug(`Failed to load icon set '${setId}':`, error)
     }

62-88: Consider caching getAllIcons() result to avoid repeated dynamic imports.

getAllIcons() performs dynamic imports for every icon set on each call. For a production API, this could be expensive if called frequently. Consider memoizing the result since icon sets don't change at runtime.

♻️ Proposed caching implementation
+let cachedIcons: IconMetadata[] | null = null
+
 export async function getAllIcons(): Promise<IconMetadata[]> {
+  if (cachedIcons) {
+    return cachedIcons
+  }
+
   const icons: IconMetadata[] = []

   for (const setId of ICON_SET_IDS) {
     // ... existing code ...
   }

+  cachedIcons = icons
   return icons
 }
src/__tests__/pages/api/__tests__/[version]/icons/[iconName].test.ts (1)

17-24: Mock duplicates production logic - consider importing the real function.

The parseIconId mock reimplements the actual logic, which could lead to test/implementation drift. Consider using Jest's requireActual to import the real function while mocking only what's necessary.

♻️ Proposed: Use real parseIconId implementation
 jest.mock('../../../../../../utils/icons/reactIcons', () => ({
   getIconSvg: jest.fn((setId: string, iconName: string) => {
     if (setId === 'fa' && iconName === 'FaCircle') return Promise.resolve(mockSvg)
     return Promise.resolve(null)
   }),
-  parseIconId: jest.fn((iconId: string) => {
-    const underscoreIndex = iconId.indexOf('_')
-    if (underscoreIndex <= 0) return null
-    const setId = iconId.slice(0, underscoreIndex)
-    const iconName = iconId.slice(underscoreIndex + 1)
-    if (!setId || !iconName) return null
-    return { setId, iconName }
-  }),
+  parseIconId: jest.requireActual('../../../../../../utils/icons/reactIcons').parseIconId,
 }))
src/pages/api/openapi.json.ts (2)

107-163: Consider adding 404 response for version not found.

The /{version}/icons endpoint returns 404 when the version doesn't exist (per the implementation in src/pages/api/[version]/icons/index.ts), but this isn't documented in the OpenAPI spec.

📝 Add 404 response documentation
             '200': {
               description: 'List of icons with metadata',
               // ... existing schema ...
             },
+            '404': {
+              description: 'Version not found',
+            },
           },

164-201: Consider adding 400 response for invalid icon name format.

The /{version}/icons/{icon-name} endpoint returns 400 for invalid icon name formats, but only 404 is documented. This could cause confusion for API consumers.

📝 Add 400 response documentation
           responses: {
             '200': {
               description: 'SVG markup for the icon',
               content: {
                 'image/svg+xml': {
                   schema: { type: 'string' },
                 },
               },
             },
+            '400': {
+              description: 'Invalid icon name format. Expected: {set}_{iconName}',
+            },
             '404': {
               description: 'Icon not found',
             },
           },
src/pages/api/[version]/icons/[iconName].ts (1)

17-17: Minor: Variable aliasing may cause confusion.

The destructuring { version, iconName: iconId } creates an alias, but iconName is reused later (line 53) as a different variable from parseIconId. While not a bug, this could be clearer.

♻️ Suggested clarification

Consider renaming for clarity:

-  const { version, iconName: iconId } = params
+  const { version, iconName: rawIconId } = params

And update references to iconIdrawIconId accordingly.

src/__tests__/pages/api/__tests__/[version]/icons/index.test.ts (1)

50-51: Consider using describe block and afterEach for cleaner test organization.

Tests would benefit from wrapping in a describe block and moving the repeated jest.restoreAllMocks() calls to an afterEach hook.

♻️ Suggested structure
+describe('GET /api/{version}/icons', () => {
+  afterEach(() => {
+    jest.restoreAllMocks()
+  })
+
 it('returns all icons with metadata', async () => {
   global.fetch = jest.fn(() =>
     ...
   expect(body.icons[0]).toHaveProperty('unicode')
-
-  jest.restoreAllMocks()
 })
 // ... other tests without individual restoreAllMocks calls
+})

Also applies to: 79-80

src/pages/api/[version]/icons/index.ts (1)

32-33: Consider pagination for large icon sets.

react-icons contains thousands of icons across all sets. Returning all icons in a single response could impact performance and client-side rendering. Consider adding pagination (limit/offset or cursor-based) for production use.

@dlabaj dlabaj changed the title WIP Feat (icons): Icons API end points for retrieving icons. feat (icons): Icons API end points for retrieving icons. Feb 6, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/pages/api/`[version]/icons/[iconName].ts:
- Around line 42-59: The call to fetchIconsIndex (and subsequent fetchIconSvgs)
can throw and is not wrapped, causing uncontrolled 500s; wrap the body that uses
fetchIconsIndex, icons, icon, fetchIconSvgs and svg in a try/catch around the
existing logic inside the handler, and in the catch return createJsonResponse({
error: 'Internal server error', details: error?.message ?? String(error) }, 500)
(or similar) so failures from fetchIconsIndex/fetchIconSvgs produce a clean JSON
500 response instead of an unhandled exception.
🧹 Nitpick comments (2)
src/__tests__/pages/api/__tests__/[version]/icons/index.test.ts (1)

62-87: Consider cleaning up global.fetch in an afterEach block.

jest.restoreAllMocks() only restores mocks created via jest.spyOn, not direct global.fetch assignments. If a test throws before the restoreAllMocks call, the mutated global.fetch leaks. Since each test reassigns it anyway this is low risk, but an afterEach would be cleaner.

♻️ Suggested cleanup pattern
+const originalFetch = global.fetch
+
+afterEach(() => {
+  global.fetch = originalFetch
+  jest.restoreAllMocks()
+})
+
 it('returns all icons with metadata', async () => {
   global.fetch = createFetchMock()
   // ...
-  jest.restoreAllMocks()
 })
src/utils/icons/reactIcons.ts (1)

117-145: Consider the memory and build-time cost of rendering all icons in a set.

getIconSvgsForSet renders every icon in a set to SVG at build time via renderToStaticMarkup. For large sets (e.g., Material Design has ~10,000+ icons), this could be memory-intensive during the build. Since this runs at prerender time (not in Workers), it should be fine, but worth noting if build times become a concern.

Comment on lines 42 to 59
const icons = await fetchIconsIndex(url)
const icon = icons.find((i) => i.reactName === reactName)
if (!icon?.set) {
return createJsonResponse(
{ error: `Icon '${reactName}' not found` },
404,
)
}

const svgs = await fetchIconSvgs(url, icon.set)
const svg = svgs?.[reactName] ?? null

if (!svg) {
return createJsonResponse(
{ error: `Icon '${reactName}' not found in set '${icon.set}'` },
404,
)
}
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Unhandled error from fetchIconsIndex — will produce an uncontrolled 500.

fetchIconsIndex throws on non-OK responses (see src/utils/icons/fetch.ts lines 19-23), but this call sits outside any try/catch. Compare with src/pages/api/[version]/icons/index.ts, which wraps everything in a single try/catch and returns a clean 500 JSON response.

🐛 Proposed fix: wrap the remaining logic in a try/catch
+  try {
     const icons = await fetchIconsIndex(url)
     const icon = icons.find((i) => i.reactName === reactName)
     if (!icon?.set) {
       return createJsonResponse(
         { error: `Icon '${reactName}' not found` },
         404,
       )
     }
 
     const svgs = await fetchIconSvgs(url, icon.set)
     const svg = svgs?.[reactName] ?? null
 
     if (!svg) {
       return createJsonResponse(
         { error: `Icon '${reactName}' not found in set '${icon.set}'` },
         404,
       )
     }
 
     return createSvgResponse(svg)
+  } catch (error) {
+    const details = error instanceof Error ? error.message : String(error)
+    return createJsonResponse(
+      { error: 'Failed to load icon', details },
+      500,
+    )
+  }
🤖 Prompt for AI Agents
In `@src/pages/api/`[version]/icons/[iconName].ts around lines 42 - 59, The call
to fetchIconsIndex (and subsequent fetchIconSvgs) can throw and is not wrapped,
causing uncontrolled 500s; wrap the body that uses fetchIconsIndex, icons, icon,
fetchIconSvgs and svg in a try/catch around the existing logic inside the
handler, and in the catch return createJsonResponse({ error: 'Internal server
error', details: error?.message ?? String(error) }, 500) (or similar) so
failures from fetchIconsIndex/fetchIconSvgs produce a clean JSON 500 response
instead of an unhandled exception.

Copy link
Collaborator

@kmcfaul kmcfaul Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

edit: was +1ing a coderabbit comment about putting the fetchIconsIndex and other fetching into a try/catch block

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kmcfaul, thank you for acknowledging the issue! Would you like me to help implement the fix by wrapping the fetchIconsIndex and related calls in a try/catch block? I can plan the code changes to ensure all potential errors are properly handled and return clean JSON 500 responses.

Copy link
Collaborator

@kmcfaul kmcfaul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think our api data is fetching from a different repo, the icons I'm seeing on the preview don't appear to exist when used in our docs (and vice versa for icons that we do use in the docs, which cannot be found by the api call).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't pointed at PF's icons, this is pulling a separate package.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated this to use static folder like we discussed. I ended up removing "style", "unicode", and "set" since they were not reterival from static and they offer no valuable information.

Copy link
Collaborator

@kmcfaul kmcfaul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's an issue with the api/v6/icons/{iconName} response presentation. It's trying and failing to render the svg directly, and instead returns the svg's document tree. You can highlight the text and copy it elsewhere pretty easily, but the actual HTML getting rendered is pretty gnarly and doesn't actually render the svg element. I think we should probably try to fix the rendering, if it's not a problem with how we're delivering the static svgs, or deliver the svg as a string/part of a JSON object instead.

Do we want to also return the usage metadata field for a direct icon query? It's currently only available as part of the full icons query. I guess I'm curious what the intended audience for the direct icon api is, because a react dev would probably want the import while HTML/core dev would want the svg. Maybe we have endpoints for both types? I think this second paragraph could be a followup though.

}

/**
* Parse icon id "set_iconName" into { setId, iconName }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helper doesn't seem to be in use at the moment, unless we want to consider grouping the RH icons into separate sets logically.

icons.push({
name,
reactName,
usage: `import { ${reactName} } from '@patternfly/react-icons'`,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just want to note that usually our icons are imported directly from the dist, but I think long term we want to encourage importing from the overall package instead. So this doesn't need any updates.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to separate out the RH icon sets from the PF icons here? They are prefixed with their set name (rh-ui, rh-micron, and rh-standard) in the file names. I'd probably just lump the rest of the icons under pf, though I'm unsure if some of our icons are just font-awesome icons.

If not, we can probably refactor out the set logic that's around in this file.

Comment on lines 42 to 59
const icons = await fetchIconsIndex(url)
const icon = icons.find((i) => i.reactName === reactName)
if (!icon?.set) {
return createJsonResponse(
{ error: `Icon '${reactName}' not found` },
404,
)
}

const svgs = await fetchIconSvgs(url, icon.set)
const svg = svgs?.[reactName] ?? null

if (!svg) {
return createJsonResponse(
{ error: `Icon '${reactName}' not found in set '${icon.set}'` },
404,
)
}
Copy link
Collaborator

@kmcfaul kmcfaul Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

edit: was +1ing a coderabbit comment about putting the fetchIconsIndex and other fetching into a try/catch block

Copy link
Contributor

@wise-king-sullyman wise-king-sullyman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned elsewhere it seems like https://icons-api.patternfly-doc-core.pages.dev/api/v6/icons is still throwing a 500 unfortunately.

A few other comments:

@@ -0,0 +1,42 @@
import type { APIRoute, GetStaticPaths } from 'astro'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this file name being .json.ts feels weird?

@@ -0,0 +1,30 @@
import type { APIRoute } from 'astro'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeat comment about the .json.ts file name.


/** Resolve path to @patternfly/react-icons/dist/static. Uses cwd so it works in dev and build. */
function getStaticIconsDir(): string {
return path.join(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'll want to have this use import.meta.resolve or something, otherwise I think this method of determining the react-icons dir will have issues when pulled into a monorepo.

Comment on lines +28 to +44
/** Convert kebab-case filename (no .svg) to PatternFly React component name (PascalCase + "Icon"). */
function kebabToReactName(kebab: string): string {
const pascal = kebab
.split('-')
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
.join('')
return pascal + 'Icon'
}

/** Convert React component name back to kebab-case (without "Icon" suffix). */
function reactNameToKebab(reactName: string): string {
const withoutIcon = reactName.replace(/Icon$/, '')
return withoutIcon
.replace(/([a-z])([A-Z])/g, '$1-$2')
.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
.toLowerCase()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, could you use the change-case package for some of the logic in these?

Also could it make more sense to have them in the case util file?

return []
}

const files = fs.readdirSync(staticDir)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (and this repeats a few times in this file) since we're already having these functions setup as async I would lean towards using async readDir/readFile.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (7)
src/pages/iconsIndex.json.ts (1)

10-30: Inconsistent response construction — use createJsonResponse for uniformity.

Both the success and error paths construct new Response manually, missing the Cache-Control, Date, and Content-Length headers that createJsonResponse provides. The error path also uses String(error) (which prepends "Error: " for Error instances) rather than the error instanceof Error ? error.message : String(error) pattern used in sibling routes.

♻️ Suggested refactor
+import { createJsonResponse } from '../utils/apiHelpers'

 export const GET: APIRoute = async () => {
   try {
     const icons = await getAllIcons()
-    return new Response(JSON.stringify({ icons }), {
-      status: 200,
-      headers: { 'Content-Type': 'application/json' },
-    })
+    return createJsonResponse({ icons })
   } catch (error) {
-    return new Response(
-      JSON.stringify({ error: 'Failed to load icons index', details: String(error) }),
-      { status: 500, headers: { 'Content-Type': 'application/json' } }
-    )
+    const details = error instanceof Error ? error.message : String(error)
+    return createJsonResponse({ error: 'Failed to load icons index', details }, 500)
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/iconsIndex.json.ts` around lines 10 - 30, The GET handler currently
returns manual Response objects (wrapping getAllIcons) and on error uses
String(error); replace both success and catch branches to call the shared
createJsonResponse helper so responses include standardized headers
(Cache-Control, Date, Content-Length) and consistent serialization, and change
the error serialization to use error instanceof Error ? error.message :
String(error); update the success branch to return createJsonResponse({ icons })
and the catch branch to return createJsonResponse({ error: 'Failed to load icons
index', details: error instanceof Error ? error.message : String(error) }, 500),
referencing the GET export and getAllIcons call.
src/pages/api/[version]/icons/[setId].json.ts (1)

13-18: flatMap with a single-element array is equivalent to map here.

If the intent is to expand to multiple setId values per version in the future, a comment would clarify the choice; otherwise, simplify:

♻️ Suggested simplification
-  return versions.flatMap((version) => [
-    { params: { version, setId: 'pf' } },
-  ])
+  return versions.map((version) => ({ params: { version, setId: 'pf' } }))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/api/`[version]/icons/[setId].json.ts around lines 13 - 18, The
getStaticPaths function uses versions.flatMap returning a single-element array,
which is equivalent to map; replace flatMap with map and return the object
directly (e.g., versions.map(version => ({ params: { version, setId: 'pf' } })))
to simplify, or if you intentionally plan to expand to multiple setId values
later, keep flatMap but add a clarifying comment above getStaticPaths noting
that flatMap is used to support multiple setId entries per version in the
future; reference getStaticPaths, versions, and the setId 'pf' when making the
change.
src/utils/apiIndex/get.ts (1)

63-92: Extract shared file-reading helper to eliminate duplication with getApiIndex.

getVersionsFromIndexFile repeats the same readFile / JSON.parse / ENOENT / SyntaxError boilerplate already present in getApiIndex. A small private helper (e.g. readAndParseIndexFile) would let both callers share the I/O and error-wrapping logic while preserving their individual validation paths.

♻️ Suggested refactor
+async function readAndParseIndexFile(indexPath: string): Promise<unknown> {
+  try {
+    const content = await readFile(indexPath, 'utf-8')
+    return JSON.parse(content)
+  } catch (error) {
+    if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
+      throw new Error(
+        `API index file not found at ${indexPath}. ` +
+          'Please run the build process to generate the index.',
+      )
+    }
+    if (error instanceof SyntaxError) {
+      throw new Error(
+        `API index contains invalid JSON at ${indexPath}. Please rebuild to regenerate the index file.`,
+      )
+    }
+    throw error
+  }
+}

 export async function getApiIndex(): Promise<ApiIndex> {
   const outputDir = await getOutputDir()
   const indexPath = join(outputDir, 'apiIndex.json')
-  try {
-    const content = await readFile(indexPath, 'utf-8')
-    const parsed = JSON.parse(content)
+  const parsed = await readAndParseIndexFile(indexPath)
     // Validate index structure
     ...
-  } catch (error) { ... }
 }

 export async function getVersionsFromIndexFile(): Promise<string[]> {
   const outputDir = await getOutputDir()
   const indexPath = join(outputDir, 'apiIndex.json')
-  try {
-    const content = await readFile(indexPath, 'utf-8')
-    const parsed = JSON.parse(content)
+  const parsed = await readAndParseIndexFile(indexPath)
   if (!parsed.versions || !Array.isArray((parsed as any).versions)) { ... }
-  } catch (error) { ... }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/apiIndex/get.ts` around lines 63 - 92, Extract the duplicated
file-read/parse/error-handling logic into a small private helper (e.g.,
readAndParseIndexFile) and have both getVersionsFromIndexFile and getApiIndex
call it: move the await readFile(indexPath, 'utf-8'), JSON.parse, and the ENOENT
/ SyntaxError mapping into readAndParseIndexFile so it returns the parsed object
(or throws the same contextual errors), then update getVersionsFromIndexFile to
call readAndParseIndexFile and only perform the versions-array validation, and
update getApiIndex to call the helper and keep its own specific validation;
ensure helper uses the same indexPath construction and preserves thrown error
messages.
src/utils/icons/fetch.ts (1)

47-63: Inconsistent error model between fetchIconsIndex (throws) and fetchIconSvgs (returns null).

fetchIconSvgs returns null on non-OK HTTP responses but can still throw on network failures. Callers therefore need to handle both patterns. Aligning both functions to throw on any failure (and letting callers decide how to surface errors) would simplify usage and prevent the silent-null footgun seen in [iconName].ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/icons/fetch.ts` around lines 47 - 63, The function fetchIconSvgs
currently returns null on non-OK HTTP responses which makes its error model
inconsistent with fetchIconsIndex; change fetchIconSvgs to throw an Error on any
non-OK response (include response.status and response.statusText or response
body for context) instead of returning null, and update its return type to
Promise<Record<string, string>> so callers only handle thrown errors; keep
existing behavior for network exceptions (let them propagate) and reference the
function name fetchIconSvgs and the response/iconsSvgsUrl variables when making
the change.
src/utils/icons/reactIcons.ts (2)

50-122: getAllIcons and getIconSvgsForSet duplicate directory-reading logic.

Both functions resolve the static dir, check existence, read the directory, filter .svg files, and compute reactName. The only meaningful difference is what they accumulate. Extract a private helper to avoid this repetition:

♻️ Proposed refactor
+interface SvgFileEntry {
+  kebab: string
+  reactName: string
+  filePath: string
+}
+
+function readSvgEntries(): SvgFileEntry[] {
+  const staticDir = getStaticIconsDir()
+  if (!fs.existsSync(staticDir)) {
+    return []
+  }
+  return fs
+    .readdirSync(staticDir)
+    .filter((f) => f.endsWith('.svg'))
+    .map((file) => {
+      const kebab = file.replace(/\.svg$/, '')
+      return { kebab, reactName: kebabToReactName(kebab), filePath: path.join(staticDir, file) }
+    })
+}
+
 export async function getAllIcons(): Promise<IconMetadata[]> {
-  const staticDir = getStaticIconsDir()
-  if (!fs.existsSync(staticDir)) {
-    return []
-  }
-  const files = fs.readdirSync(staticDir)
-  const svgFiles = files.filter((f) => f.endsWith('.svg'))
-  const icons: IconMetadata[] = []
-  for (const file of svgFiles) {
-    const name = file.replace(/\.svg$/, '')
-    const reactName = kebabToReactName(name)
-    icons.push({
-      name,
-      reactName,
-      usage: `import { ${reactName} } from '@patternfly/react-icons'`,
-    })
-  }
-  return icons
+  return readSvgEntries().map(({ kebab, reactName }) => ({
+    name: kebab,
+    reactName,
+    usage: `import { ${reactName} } from '@patternfly/react-icons'`,
+  }))
 }

 export async function getIconSvgsForSet(setId: string): Promise<Record<string, string>> {
   if (setId !== PF_ICONS_SET_ID) {
     return {}
   }
-  const staticDir = getStaticIconsDir()
-  if (!fs.existsSync(staticDir)) {
-    return {}
-  }
-  const files = fs.readdirSync(staticDir)
-  const svgFiles = files.filter((f) => f.endsWith('.svg'))
   const svgs: Record<string, string> = {}
-  for (const file of svgFiles) {
-    const name = file.replace(/\.svg$/, '')
-    const reactName = kebabToReactName(name)
-    const filePath = path.join(staticDir, file)
-    const content = fs.readFileSync(filePath, 'utf-8')
-    svgs[reactName] = content.trim()
-  }
+  for (const { reactName, filePath } of readSvgEntries()) {
+    svgs[reactName] = fs.readFileSync(filePath, 'utf-8').trim()
+  }
   return svgs
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/icons/reactIcons.ts` around lines 50 - 122, Both getAllIcons and
getIconSvgsForSet duplicate the same directory-checking and SVG-file parsing
logic; extract a private helper (e.g., gatherStaticSvgFiles or listStaticSvgs)
that accepts no args or a callback and returns an array of objects like { name,
reactName, filePath } (or returns names and let caller read content), then
update getAllIcons to map that array to IconMetadata (usage string) and update
getIconSvgsForSet to read file contents from filePath and build the
Record<string,string>; ensure the helper uses getStaticIconsDir, fs.existsSync,
fs.readdirSync and kebabToReactName so callers no longer duplicate that logic.

50-51: Remove unnecessary async keywords from three synchronous functions.

All three functions (getAllIcons, getIconSvgsForSet, getIconSvg) are declared async but perform only synchronous filesystem operations with no await inside. The async keyword is misleading—it suggests deferred work where none exists. The callers simply await the results, which works identically whether the functions return Promise<T> or T directly.

Remove async and adjust return types:

Diff
-export async function getAllIcons(): Promise<IconMetadata[]> {
+export function getAllIcons(): IconMetadata[] {

-export async function getIconSvgsForSet(setId: string): Promise<Record<string, string>> {
+export function getIconSvgsForSet(setId: string): Record<string, string> {

-export async function getIconSvg(setId: string, iconName: string): Promise<string | null> {
+export function getIconSvg(setId: string, iconName: string): string | null {

Also applies to: 97-98, 129-131

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/icons/reactIcons.ts` around lines 50 - 51, Remove the unnecessary
async keyword from getAllIcons, getIconSvgsForSet, and getIconSvg because they
perform only synchronous filesystem work and contain no awaits; change their
signatures to return the synchronous types (e.g., IconMetadata[] or
string[]/string as appropriate) instead of Promise<...>, delete the async
modifier from the function declarations, and update any internal return
statements to return values directly (no Promise.resolve), ensuring type
annotations for each function (getAllIcons, getIconSvgsForSet, getIconSvg)
reflect the non-Promise return type so callers and TypeScript types remain
correct.
src/pages/api/openapi.json.ts (1)

162-199: {icon-name} parameter name inconsistency and incorrect description example.

Two issues:

  1. icon-name (hyphenated) is the only path parameter in this entire spec that isn't a single word or camelCase — all others use version, section, page, tab, etc. Hyphenated names can cause inconsistent variable generation in OpenAPI client generators.
  2. The description repeats the same incorrect examples from index.ts: FaCircle and MdHome are Font Awesome / Material Design icons, not PatternFly icons. Update to CircleIcon or AccessibleIconIcon.
-          description:
-            'Returns actual SVG markup for the icon. Icon name: React component name (e.g., FaCircle, MdHome)',
+          description:
+            'Returns actual SVG markup for the icon. Icon name: PatternFly React component name (e.g., CircleIcon, AccessibleIconIcon)',
-            {
-              name: 'icon-name',
+            {
+              name: 'iconName',

(and update the path template /{version}/icons/{iconName} to match)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/api/openapi.json.ts` around lines 162 - 199, Update the openapi
entry for getIconSvg: rename the path template from
'/{version}/icons/{icon-name}' to '/{version}/icons/{iconName}', change the path
parameter object key from 'icon-name' to 'iconName' (keeping in-path
required/schema), and update the parameter description/example to use PatternFly
icon names such as 'CircleIcon' or 'AccessibleIconIcon' instead of
'FaCircle'/'MdHome' so the operationId getIconSvg and client code generation use
a consistent camelCase parameter name.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 62: The package entry for `@patternfly/react-icons` is currently pinned to
the prerelease "6.5.0-prerelease.13"; change the dependency version to a caret
range (e.g., "^6.5.0") once a stable 6.5.x or higher is published, and create a
short follow-up issue or TODO in the repo to track this upgrade; reference the
dependency name "@patternfly/react-icons" and the current prerelease pin
"6.5.0-prerelease.13" when creating the issue so reviewers can verify the
replacement and future-proof the versioning.
- Line 77: Remove the unused dependency "react-icons" from package.json: open
package.json, locate the dependency entry "react-icons": "^5.5.0" and delete
that line, then run your package manager (npm/yarn/pnpm) to update lockfile and
node_modules (e.g., npm install or yarn install) to ensure the removal is
applied; verify no imports reference react-icons and that builds/tests still
pass.

In `@src/__tests__/pages/api/__tests__/`[version]/icons/[iconName].test.ts:
- Around line 22-45: The tests assign global.fetch directly (via
createFetchMock) which jest.restoreAllMocks() won't revert; save the original
fetch before you override it and restore it in afterEach, or replace direct
assignments with jest.spyOn(global,
'fetch').mockImplementation(createFetchMock()) so restoreAllMocks() works —
update the test file to capture originalFetch = global.fetch (in beforeEach/at
top), set global.fetch = createFetchMock() or use jest.spyOn for each test, and
in afterEach call jest.restoreAllMocks() and if you saved originalFetch reassign
global.fetch = originalFetch to ensure no mock leaks.

In `@src/__tests__/pages/api/__tests__/`[version]/icons/index.test.ts:
- Around line 53-184: Tests assign global.fetch directly with createFetchMock()
but rely on jest.restoreAllMocks(), which doesn't revert direct assignments.
Capture the original fetch (e.g., const originalFetch = global.fetch) and add an
afterEach hook that restores it (global.fetch = originalFetch), or change the
tests to use jest.spyOn(global, 'fetch') and call jest.restoreAllMocks() in
afterEach; update the test cases referencing global.fetch and
jest.restoreAllMocks() accordingly to ensure no mock leaks between tests.
- Around line 96-109: The test 'filter is case-insensitive' uses a weak
assertion; update the GET test (the test function invoking GET with
params.version 'v6' and URL '?filter=CIRCLE') to assert the exact expected
number of matches (expect(body.icons.length).toBe(2)) based on the mock data and
additionally verify returned icon entries actually match "circle" (e.g., assert
each icon.name or relevant property contains 'circle' case-insensitively) to
guard against regressions.

In `@src/pages/api/`[version]/icons/[iconName].ts:
- Around line 52-59: The code conflates a null return from fetchIconSvgs (an
asset fetch failure) with a missing icon; change the check so if svgs === null
you return a server-error JSON (e.g., createJsonResponse({ error: `Failed to
fetch SVG assets for '${reactName}'` }, 502)) instead of the "Icon ... not
found" 404, while keeping the existing 404 branch for when svgs is non-null but
svgs[reactName] is missing; reference fetchIconSvgs, svgs, reactName and
createJsonResponse to locate and update the logic.

In `@src/pages/api/index.ts`:
- Around line 420-426: Update the 'icon-name' parameter documentation to
reference `@patternfly/react-icons` naming (the <Name>Icon pattern) instead of
Font Awesome/Material Design; change the description string in the API spec that
currently mentions "FaCircle, MdHome" to something like "PatternFly icon
component name (e.g., CircleIcon, AccessibleIconIcon)" and update the example
value to a PatternFly icon name (e.g., "CircleIcon") so the parameter (named
'icon-name') correctly reflects the `@patternfly/react-icons` naming convention
used by the route.

---

Duplicate comments:
In `@src/pages/api/`[version]/icons/[iconName].ts:
- Around line 43-60: Wrap the calls to fetchIconsIndex(url, assetsFetch) and
fetchIconSvgs(url, version, 'pf', assetsFetch) in a try/catch so any thrown
errors (network/non-OK responses) are handled instead of bubbling to an
uncontrolled 500; inside the catch return a createJsonResponse with a clear
error payload and non-OK status (e.g. 502 or 500) so the route returns a
controlled error. Target the block that assigns icons and svgs and the
subsequent lookups of icon and svg (symbols: fetchIconsIndex, icons,
fetchIconSvgs, svgs, createJsonResponse).

---

Nitpick comments:
In `@src/pages/api/`[version]/icons/[setId].json.ts:
- Around line 13-18: The getStaticPaths function uses versions.flatMap returning
a single-element array, which is equivalent to map; replace flatMap with map and
return the object directly (e.g., versions.map(version => ({ params: { version,
setId: 'pf' } }))) to simplify, or if you intentionally plan to expand to
multiple setId values later, keep flatMap but add a clarifying comment above
getStaticPaths noting that flatMap is used to support multiple setId entries per
version in the future; reference getStaticPaths, versions, and the setId 'pf'
when making the change.

In `@src/pages/api/openapi.json.ts`:
- Around line 162-199: Update the openapi entry for getIconSvg: rename the path
template from '/{version}/icons/{icon-name}' to '/{version}/icons/{iconName}',
change the path parameter object key from 'icon-name' to 'iconName' (keeping
in-path required/schema), and update the parameter description/example to use
PatternFly icon names such as 'CircleIcon' or 'AccessibleIconIcon' instead of
'FaCircle'/'MdHome' so the operationId getIconSvg and client code generation use
a consistent camelCase parameter name.

In `@src/pages/iconsIndex.json.ts`:
- Around line 10-30: The GET handler currently returns manual Response objects
(wrapping getAllIcons) and on error uses String(error); replace both success and
catch branches to call the shared createJsonResponse helper so responses include
standardized headers (Cache-Control, Date, Content-Length) and consistent
serialization, and change the error serialization to use error instanceof Error
? error.message : String(error); update the success branch to return
createJsonResponse({ icons }) and the catch branch to return
createJsonResponse({ error: 'Failed to load icons index', details: error
instanceof Error ? error.message : String(error) }, 500), referencing the GET
export and getAllIcons call.

In `@src/utils/apiIndex/get.ts`:
- Around line 63-92: Extract the duplicated file-read/parse/error-handling logic
into a small private helper (e.g., readAndParseIndexFile) and have both
getVersionsFromIndexFile and getApiIndex call it: move the await
readFile(indexPath, 'utf-8'), JSON.parse, and the ENOENT / SyntaxError mapping
into readAndParseIndexFile so it returns the parsed object (or throws the same
contextual errors), then update getVersionsFromIndexFile to call
readAndParseIndexFile and only perform the versions-array validation, and update
getApiIndex to call the helper and keep its own specific validation; ensure
helper uses the same indexPath construction and preserves thrown error messages.

In `@src/utils/icons/fetch.ts`:
- Around line 47-63: The function fetchIconSvgs currently returns null on non-OK
HTTP responses which makes its error model inconsistent with fetchIconsIndex;
change fetchIconSvgs to throw an Error on any non-OK response (include
response.status and response.statusText or response body for context) instead of
returning null, and update its return type to Promise<Record<string, string>> so
callers only handle thrown errors; keep existing behavior for network exceptions
(let them propagate) and reference the function name fetchIconSvgs and the
response/iconsSvgsUrl variables when making the change.

In `@src/utils/icons/reactIcons.ts`:
- Around line 50-122: Both getAllIcons and getIconSvgsForSet duplicate the same
directory-checking and SVG-file parsing logic; extract a private helper (e.g.,
gatherStaticSvgFiles or listStaticSvgs) that accepts no args or a callback and
returns an array of objects like { name, reactName, filePath } (or returns names
and let caller read content), then update getAllIcons to map that array to
IconMetadata (usage string) and update getIconSvgsForSet to read file contents
from filePath and build the Record<string,string>; ensure the helper uses
getStaticIconsDir, fs.existsSync, fs.readdirSync and kebabToReactName so callers
no longer duplicate that logic.
- Around line 50-51: Remove the unnecessary async keyword from getAllIcons,
getIconSvgsForSet, and getIconSvg because they perform only synchronous
filesystem work and contain no awaits; change their signatures to return the
synchronous types (e.g., IconMetadata[] or string[]/string as appropriate)
instead of Promise<...>, delete the async modifier from the function
declarations, and update any internal return statements to return values
directly (no Promise.resolve), ensuring type annotations for each function
(getAllIcons, getIconSvgsForSet, getIconSvg) reflect the non-Promise return type
so callers and TypeScript types remain correct.

"@patternfly/react-core": "^6.0.0",
"@patternfly/react-drag-drop": "^6.0.0",
"@patternfly/react-icons": "^6.0.0",
"@patternfly/react-icons": "6.5.0-prerelease.13",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

@patternfly/react-icons 6.5.0 stable release npm

💡 Result:

A stable @patternfly/react-icons@6.5.0 is not published on npm (no latest/stable tag pointing to 6.5.0).

The most recent stable (latest) version shown by public registries/CDN mirrors is 6.4.0 (published Oct 16, 2025). (classic.yarnpkg.com)

Citations:


Track @patternfly/react-icons prerelease pin for upgrade to stable.

6.5.0-prerelease.13 is an exact pin to a prerelease build. Prerelease packages carry no semver stability guarantees — APIs can change between iterations without a major bump — and this pin receives no automatic patch or security updates. While the commit notes this is intentional to access static icons, it introduces supply-chain fragility.

Once a stable 6.5.x (or higher) is published, this should be updated to a caret range (e.g., "^6.5.0"). Consider tracking this with a TODO comment or a follow-up issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 62, The package entry for `@patternfly/react-icons` is
currently pinned to the prerelease "6.5.0-prerelease.13"; change the dependency
version to a caret range (e.g., "^6.5.0") once a stable 6.5.x or higher is
published, and create a short follow-up issue or TODO in the repo to track this
upgrade; reference the dependency name "@patternfly/react-icons" and the current
prerelease pin "6.5.0-prerelease.13" when creating the issue so reviewers can
verify the replacement and future-proof the versioning.

"react-docgen": "^7.1.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^6.0.0",
"react-icons": "^5.5.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check where react-icons is imported in the repo (excluding node_modules)
rg -rn "from ['\"]react-icons" --glob '!**/node_modules/**' --glob '!package.json' -A 2

Repository: patternfly/patternfly-doc-core

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Also check for require statements and any other references to react-icons
rg -rn "react-icons" --glob '!**/node_modules/**' --glob '!package-lock.json' --glob '!yarn.lock' | head -50

Repository: patternfly/patternfly-doc-core

Length of output: 2725


🏁 Script executed:

#!/bin/bash
# Check the actual package.json to see all dependencies and their context
cat -n package.json | grep -A 5 -B 5 "react-icons"

Repository: patternfly/patternfly-doc-core

Length of output: 1004


🏁 Script executed:

#!/bin/bash
# Search more broadly for any reference to react-icons, including comments, configs, etc.
rg -i "react-icons" --glob '!node_modules' --glob '!dist' --glob '!build' -t json -t js -t ts -t tsx -t jsx

Repository: patternfly/patternfly-doc-core

Length of output: 101


🏁 Script executed:

#!/bin/bash
# Check if it's perhaps a transitive dependency by looking for import statements in a different way
rg "import.*from.*react-icons|require.*react-icons" --glob '!node_modules'

Repository: patternfly/patternfly-doc-core

Length of output: 2646


🏁 Script executed:

#!/bin/bash
# List all source files to understand the codebase structure
git ls-files | grep -E "\.(ts|tsx|js|jsx)$" | head -20

Repository: patternfly/patternfly-doc-core

Length of output: 633


Remove unused react-icons dependency from package.json.

The codebase uses only @patternfly/react-icons for all icon imports. The standalone react-icons package (^5.5.0) at line 77 is not imported or referenced anywhere and should be removed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 77, Remove the unused dependency "react-icons" from
package.json: open package.json, locate the dependency entry "react-icons":
"^5.5.0" and delete that line, then run your package manager (npm/yarn/pnpm) to
update lockfile and node_modules (e.g., npm install or yarn install) to ensure
the removal is applied; verify no imports reference react-icons and that
builds/tests still pass.

Comment on lines +22 to +45
function createFetchMock(): typeof fetch {
return jest.fn((input: RequestInfo | URL) => {
const url = typeof input === 'string' ? input : input.toString()
if (url.includes('/iconsIndex.json')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve(mockIconsIndex),
} as Response)
}
const match = url.match(/\/api\/[^/]+\/icons\/([^/]+)\.json/)
if (match) {
const setId = match[1]
const svgs = mockIconSvgs[setId] ?? {}
return Promise.resolve({
ok: true,
json: () => Promise.resolve(svgs),
} as Response)
}
return Promise.resolve({
ok: true,
json: () => Promise.resolve(mockApiIndex),
} as Response)
}) as typeof fetch
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

jest.restoreAllMocks() does not restore direct global.fetch assignments.

jest.restoreAllMocks() only reverts mocks created with jest.spyOn. Since every test assigns global.fetch = jest.fn(...) directly, restoreAllMocks() is a no-op for fetch cleanup — the mock leaks to any subsequent test files that share this global. Additionally, placing cleanup inside the test body means it won't execute when an assertion throws.

The standard fix is to save the original reference and restore it in afterEach, or use jest.spyOn:

🛠️ Proposed fix
+const originalFetch = global.fetch

+afterEach(() => {
+  global.fetch = originalFetch
+})

 it('returns SVG markup for valid icon', async () => {
   global.fetch = createFetchMock()
   ...
-  jest.restoreAllMocks()
 })

 it('returns 404 when icon is not found', async () => {
   global.fetch = createFetchMock()
   ...
-  jest.restoreAllMocks()
 })
 // ... remove jest.restoreAllMocks() from all remaining test bodies

Alternatively, use jest.spyOn so that restoreAllMocks works as intended:

+afterEach(() => {
+  jest.restoreAllMocks()
+})

 it('returns SVG markup for valid icon', async () => {
-  global.fetch = createFetchMock()
+  jest.spyOn(global, 'fetch').mockImplementation(createFetchMock())
   ...
-  jest.restoreAllMocks()
 })

Also applies to: 47-176

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/pages/api/__tests__/`[version]/icons/[iconName].test.ts around
lines 22 - 45, The tests assign global.fetch directly (via createFetchMock)
which jest.restoreAllMocks() won't revert; save the original fetch before you
override it and restore it in afterEach, or replace direct assignments with
jest.spyOn(global, 'fetch').mockImplementation(createFetchMock()) so
restoreAllMocks() works — update the test file to capture originalFetch =
global.fetch (in beforeEach/at top), set global.fetch = createFetchMock() or use
jest.spyOn for each test, and in afterEach call jest.restoreAllMocks() and if
you saved originalFetch reassign global.fetch = originalFetch to ensure no mock
leaks.

Comment on lines +53 to +184
it('returns all icons with metadata', async () => {
global.fetch = createFetchMock()

const response = await GET({
params: { version: 'v6' },
url: new URL('http://localhost:4321/api/v6/icons'),
} as any)
const body = await response.json()

expect(response.status).toBe(200)
expect(response.headers.get('Content-Type')).toBe(
'application/json; charset=utf-8',
)
expect(body).toHaveProperty('icons')
expect(body).toHaveProperty('total')
expect(Array.isArray(body.icons)).toBe(true)
expect(body.icons).toHaveLength(3)
expect(body.total).toBe(3)
expect(body.icons[0]).toHaveProperty('name')
expect(body.icons[0]).toHaveProperty('reactName')
expect(body.icons[0]).toHaveProperty('usage')

jest.restoreAllMocks()
})

it('filters icons when filter parameter is provided', async () => {
global.fetch = createFetchMock()

const response = await GET({
params: { version: 'v6' },
url: new URL('http://localhost:4321/api/v6/icons?filter=circle'),
} as any)
const body = await response.json()

expect(response.status).toBe(200)
expect(body.icons).toHaveLength(2)
expect(body.total).toBe(2)
expect(body.filter).toBe('circle')
expect(body.icons.every((i: { name: string }) => i.name.includes('circle'))).toBe(true)

jest.restoreAllMocks()
})

it('filter is case-insensitive', async () => {
global.fetch = createFetchMock()

const response = await GET({
params: { version: 'v6' },
url: new URL('http://localhost:4321/api/v6/icons?filter=CIRCLE'),
} as any)
const body = await response.json()

expect(response.status).toBe(200)
expect(body.icons.length).toBeGreaterThan(0)

jest.restoreAllMocks()
})

it('returns empty icons array when filter yields no matches', async () => {
global.fetch = createFetchMock()

const response = await GET({
params: { version: 'v6' },
url: new URL('http://localhost:4321/api/v6/icons?filter=nonexistent'),
} as any)
const body = await response.json()

expect(response.status).toBe(200)
expect(body.icons).toHaveLength(0)
expect(body.total).toBe(0)

jest.restoreAllMocks()
})

it('returns 404 error for nonexistent version', async () => {
global.fetch = createFetchMock()

const response = await GET({
params: { version: 'v99' },
url: new URL('http://localhost:4321/api/v99/icons'),
} as any)
const body = await response.json()

expect(response.status).toBe(404)
expect(body).toHaveProperty('error')
expect(body.error).toContain('v99')
expect(body.error).toContain('not found')

jest.restoreAllMocks()
})

it('returns 400 error when version parameter is missing', async () => {
global.fetch = createFetchMock()

const response = await GET({
params: {},
url: new URL('http://localhost:4321/api/icons'),
} as any)
const body = await response.json()

expect(response.status).toBe(400)
expect(body).toHaveProperty('error')
expect(body.error).toContain('Version parameter is required')

jest.restoreAllMocks()
})

it('returns 500 error when fetchIconsIndex throws', async () => {
global.fetch = jest.fn((input: RequestInfo | URL) => {
const url = typeof input === 'string' ? input : input.toString()
if (url.includes('iconsIndex.json')) {
return Promise.resolve({ ok: false, status: 500, statusText: 'Internal Server Error' } as Response)
}
return Promise.resolve({
ok: true,
json: () => Promise.resolve(mockApiIndex),
} as Response)
}) as typeof fetch

const response = await GET({
params: { version: 'v6' },
url: new URL('http://localhost:4321/api/v6/icons'),
} as any)
const body = await response.json()

expect(response.status).toBe(500)
expect(body).toHaveProperty('error')
expect(body.error).toBe('Failed to load icons')
expect(body).toHaveProperty('details')

jest.restoreAllMocks()
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

jest.restoreAllMocks() does not restore global.fetch direct assignments.

restoreAllMocks only resets spies created with jest.spyOn. Direct assignments like global.fetch = createFetchMock() are not reverted. If any assertion throws before the restoreAllMocks call, the mock leaks into subsequent tests. Prefer afterEach with explicit cleanup:

+afterEach(() => {
+  jest.restoreAllMocks()
+})
+
 it('returns all icons with metadata', async () => {
   global.fetch = createFetchMock()
   // ...
-  jest.restoreAllMocks()
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/pages/api/__tests__/`[version]/icons/index.test.ts around lines
53 - 184, Tests assign global.fetch directly with createFetchMock() but rely on
jest.restoreAllMocks(), which doesn't revert direct assignments. Capture the
original fetch (e.g., const originalFetch = global.fetch) and add an afterEach
hook that restores it (global.fetch = originalFetch), or change the tests to use
jest.spyOn(global, 'fetch') and call jest.restoreAllMocks() in afterEach; update
the test cases referencing global.fetch and jest.restoreAllMocks() accordingly
to ensure no mock leaks between tests.

Comment on lines +96 to +109
it('filter is case-insensitive', async () => {
global.fetch = createFetchMock()

const response = await GET({
params: { version: 'v6' },
url: new URL('http://localhost:4321/api/v6/icons?filter=CIRCLE'),
} as any)
const body = await response.json()

expect(response.status).toBe(200)
expect(body.icons.length).toBeGreaterThan(0)

jest.restoreAllMocks()
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Case-insensitive filter test has a weak assertion.

body.icons.length > 0 will pass as long as at least one icon is returned. Given the known mock data (2 matches for "circle"), assert the exact count and verify content to catch regressions.

🛡️ Proposed fix
   expect(response.status).toBe(200)
-  expect(body.icons.length).toBeGreaterThan(0)
+  expect(body.icons).toHaveLength(2)
+  expect(body.total).toBe(2)
+  expect(
+    body.icons.every((i: { name: string }) => i.name.toLowerCase().includes('circle'))
+  ).toBe(true)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/pages/api/__tests__/`[version]/icons/index.test.ts around lines
96 - 109, The test 'filter is case-insensitive' uses a weak assertion; update
the GET test (the test function invoking GET with params.version 'v6' and URL
'?filter=CIRCLE') to assert the exact expected number of matches
(expect(body.icons.length).toBe(2)) based on the mock data and additionally
verify returned icon entries actually match "circle" (e.g., assert each
icon.name or relevant property contains 'circle' case-insensitively) to guard
against regressions.

Comment on lines +52 to +59
const svgs = await fetchIconSvgs(url, version, 'pf', assetsFetch)
const svg = svgs?.[reactName] ?? null

if (!svg) {
return createJsonResponse(
{ error: `Icon '${reactName}' not found` },
404,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

null from fetchIconSvgs returns a misleading 404 "not found".

When fetchIconSvgs returns null (non-OK HTTP response from the SVG asset endpoint), the route returns { error: "Icon 'X' not found" } with 404. However, the icon's existence was already validated on lines 44–50. A null result here means the SVG asset couldn't be fetched—a server-side failure, not a missing icon. This conflation makes debugging harder and surfaces incorrect status codes to callers.

Once the try/catch above is in place this will be handled as a 500 naturally, but a targeted fix is to distinguish the two cases:

-  const svgs = await fetchIconSvgs(url, version, 'pf', assetsFetch)
-  const svg = svgs?.[reactName] ?? null
-
-  if (!svg) {
-    return createJsonResponse(
-      { error: `Icon '${reactName}' not found` },
-      404,
-    )
-  }
+  const svgs = await fetchIconSvgs(url, version, 'pf', assetsFetch)
+  if (!svgs) {
+    return createJsonResponse({ error: 'Failed to load icon SVGs' }, 500)
+  }
+  const svg = svgs[reactName] ?? null
+  if (!svg) {
+    return createJsonResponse(
+      { error: `Icon '${reactName}' not found in set` },
+      404,
+    )
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const svgs = await fetchIconSvgs(url, version, 'pf', assetsFetch)
const svg = svgs?.[reactName] ?? null
if (!svg) {
return createJsonResponse(
{ error: `Icon '${reactName}' not found` },
404,
)
const svgs = await fetchIconSvgs(url, version, 'pf', assetsFetch)
if (!svgs) {
return createJsonResponse({ error: 'Failed to load icon SVGs' }, 500)
}
const svg = svgs[reactName] ?? null
if (!svg) {
return createJsonResponse(
{ error: `Icon '${reactName}' not found in set` },
404,
)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/api/`[version]/icons/[iconName].ts around lines 52 - 59, The code
conflates a null return from fetchIconSvgs (an asset fetch failure) with a
missing icon; change the check so if svgs === null you return a server-error
JSON (e.g., createJsonResponse({ error: `Failed to fetch SVG assets for
'${reactName}'` }, 502)) instead of the "Icon ... not found" 404, while keeping
the existing 404 branch for when svgs is non-null but svgs[reactName] is
missing; reference fetchIconSvgs, svgs, reactName and createJsonResponse to
locate and update the logic.

Comment on lines +420 to +426
name: 'icon-name',
in: 'path',
required: true,
type: 'string',
description: 'Icon identifier: React component name (e.g., FaCircle, MdHome)',
example: 'FaCircle',
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

icon-name description and example use Font Awesome/Material Design naming conventions, not PatternFly.

FaCircle (Font Awesome) and MdHome (Material Design) are incorrect examples; the route serves only @patternfly/react-icons icons, whose React component names follow the <Name>Icon pattern (e.g., CircleIcon, AccessibleIconIcon).

-            description: 'Icon identifier: React component name (e.g., FaCircle, MdHome)',
-            example: 'FaCircle',
+            description: 'Icon identifier: PatternFly React component name (e.g., CircleIcon, AccessibleIconIcon)',
+            example: 'CircleIcon',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
name: 'icon-name',
in: 'path',
required: true,
type: 'string',
description: 'Icon identifier: React component name (e.g., FaCircle, MdHome)',
example: 'FaCircle',
},
name: 'icon-name',
in: 'path',
required: true,
type: 'string',
description: 'Icon identifier: PatternFly React component name (e.g., CircleIcon, AccessibleIconIcon)',
example: 'CircleIcon',
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/api/index.ts` around lines 420 - 426, Update the 'icon-name'
parameter documentation to reference `@patternfly/react-icons` naming (the
<Name>Icon pattern) instead of Font Awesome/Material Design; change the
description string in the API spec that currently mentions "FaCircle, MdHome" to
something like "PatternFly icon component name (e.g., CircleIcon,
AccessibleIconIcon)" and update the example value to a PatternFly icon name
(e.g., "CircleIcon") so the parameter (named 'icon-name') correctly reflects the
`@patternfly/react-icons` naming convention used by the route.

"react-docgen": "^7.1.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^6.0.0",
"react-icons": "^5.5.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably want to remove this import

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/utils/icons/icons.ts (1)

1-7: Inconsistent indentation throughout the file.

Interface body uses 4-space indent, closing } uses 2-space, and PF_ICONS_SET_ID has 2 leading spaces at module scope.

🧹 Proposed fix
-export interface IconMetadata {
-    name: string
-    reactName: string
-    usage: string
-  }
-  
-  export const PF_ICONS_SET_ID = 'pf'
+export interface IconMetadata {
+  name: string
+  reactName: string
+  usage: string
+}
+
+export const PF_ICONS_SET_ID = 'pf'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/icons/icons.ts` around lines 1 - 7, Fix inconsistent indentation in
this module: normalize the IconMetadata interface block and the module-scope
constant to use a consistent indentation style (prefer 2 spaces or 4 spaces per
project convention). Update the opening and closing braces and the properties
inside IconMetadata (name, reactName, usage) so their indentation matches, and
align the PF_ICONS_SET_ID declaration with the module top-level indentation;
verify no stray spaces before tokens and run the project's formatter/linter (or
format with the same spacing rule used in surrounding files) to ensure
consistency.
src/pages/api/[version]/icons/[iconSet].ts (1)

13-18: flatMap with a single-element array is equivalent to map here.

♻️ Simplification
-  return versions.flatMap((version) => [
-    { params: { version, iconSet: 'pf' } },
-  ])
+  return versions.map((version) => ({ params: { version, iconSet: 'pf' } }))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/api/`[version]/icons/[iconSet].ts around lines 13 - 18, The use of
Array.prototype.flatMap in getStaticPaths is unnecessary because each version
maps to a single-element array; replace the flatMap usage with a simple map over
the versions returned by getVersionsFromIndexFile so getStaticPaths returns
versions.map(version => ({ params: { version, iconSet: 'pf' } })); update the
function body of getStaticPaths accordingly to use map instead of flatMap.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/__tests__/pages/api/__tests__/`[version]/icons/[iconName].test.ts:
- Around line 31-38: The URL regex used to detect SVG fetches requires a
trailing ".json" and therefore never matches the actual Astro endpoint; update
the url.match call that uses /\/api\/[^/]+\/icons\/([^/]+)\.json/ to remove the
"\.json" so it matches /api/{version}/icons/{iconSet} (e.g. use
/\/api\/[^/]+\/icons\/([^/]+)/), keeping the same capture group so setId and the
subsequent lookup into mockIconSvgs still work and the test exercises the
intended SVG branch instead of falling back to mockApiIndex.

In `@src/utils/icons/fetch.ts`:
- Around line 39-53: The function fetchIconSvgs is missing the optional
assetsFetch parameter used in its body; update its signature to accept a fourth
optional parameter (e.g., assetsFetch?: (req: Request) => Promise<Response>) so
the internal conditional (assetsFetch ? await assetsFetch(new Request(...)) :
await fetch(...)) uses the passed-in function, and ensure callers like the one
in src/pages/api/[version]/icons/[iconName].ts continue passing assetsFetch;
keep the return behavior unchanged.

In `@wrangler.jsonc`:
- Line 4: compatibility_date was bumped from 2025-06-17 to 2026-02-18 (the
"compatibility_date" key) which crosses several Node.js/runtime breaking changes
when "nodejs_compat" is enabled; before merging, run integration and smoke tests
exercising HTTP handlers, MessageChannel/MessagePort usage, EventTarget-bound
handlers (verify correct `this`), and caching behavior (cache_no_cache_enabled)
against a worker runtime with the new date, and if any regressions appear either
revert the compatibility_date, explicitly set/lock "nodejs_compat" feature flags
to the expected pre-2025 behavior, or update the worker code paths to follow the
post-2025 semantics.

---

Duplicate comments:
In `@src/__tests__/pages/api/__tests__/`[version]/icons/[iconName].test.ts:
- Around line 22-45: The tests currently replace global.fetch directly using the
mock returned by createFetchMock(), but calling jest.restoreAllMocks() does not
restore direct assignments to global.fetch; save the original global.fetch
before overriding (const originalFetch = global.fetch) or prefer using
jest.spyOn(global, 'fetch').mockImplementation(createFetchMock()) so
jest.restoreAllMocks() will work, and then restore in an afterEach block
(global.fetch = originalFetch or jest.restoreAllMocks()); update any test files
that call createFetchMock() to use this pattern and ensure cleanup in afterEach.

In `@src/pages/api/`[version]/icons/[iconName].ts:
- Around line 42-49: The fetchIconsIndex call and subsequent lookup (const icons
= await fetchIconsIndex(url); const icon = icons.find(...)) are unprotected by
try/catch; wrap the await fetchIconsIndex(url) and the icon lookup in a
try/catch block around the code in the handler (referencing fetchIconsIndex,
icons, icon, reactName, and createJsonResponse), and in the catch call
createJsonResponse with a 500 status and include an error message (and error
details) so runtime failures return a proper JSON error response instead of
crashing.
- Around line 51-58: Restore the missing assetsFetch parameter on fetchIconSvgs
(in src/utils/icons/fetch.ts) so its signature is fetchIconSvgs(url, version,
setId, assetsFetch) and use that parameter in the function body/JSDoc; then in
the [iconName].ts route, thread locals through the handler and pass the runtime
binding (e.g. locals.runtime.env.ASSETS.fetch) into fetchIconSvgs instead of the
unknown bare identifier assetsFetch; finally, distinguish a null return (meaning
asset fetch failure) from a genuinely missing icon by returning a 502/503 error
like createJsonResponse({ error: 'Failed fetching icon assets' }, 502) when svgs
=== null, but keep returning the 404 only when svgs is non-null and the specific
reactName key is absent.

---

Nitpick comments:
In `@src/pages/api/`[version]/icons/[iconSet].ts:
- Around line 13-18: The use of Array.prototype.flatMap in getStaticPaths is
unnecessary because each version maps to a single-element array; replace the
flatMap usage with a simple map over the versions returned by
getVersionsFromIndexFile so getStaticPaths returns versions.map(version => ({
params: { version, iconSet: 'pf' } })); update the function body of
getStaticPaths accordingly to use map instead of flatMap.

In `@src/utils/icons/icons.ts`:
- Around line 1-7: Fix inconsistent indentation in this module: normalize the
IconMetadata interface block and the module-scope constant to use a consistent
indentation style (prefer 2 spaces or 4 spaces per project convention). Update
the opening and closing braces and the properties inside IconMetadata (name,
reactName, usage) so their indentation matches, and align the PF_ICONS_SET_ID
declaration with the module top-level indentation; verify no stray spaces before
tokens and run the project's formatter/linter (or format with the same spacing
rule used in surrounding files) to ensure consistency.

Comment on lines +31 to +38
const match = url.match(/\/api\/[^/]+\/icons\/([^/]+)\.json/)
if (match) {
const setId = match[1]
const svgs = mockIconSvgs[setId] ?? {}
return Promise.resolve({
ok: true,
json: () => Promise.resolve(svgs),
} as Response)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Astro static file endpoint naming convention .ts extension URL

💡 Result:

In Astro, .ts (and .js) files in src/pages/ are endpoints. Their .ts/.js extension is not part of the URL—Astro strips it during the build, so routing is based on the file path without that final extension. [1], [2]

Naming convention for “static file endpoints” (when you want a .json, .xml, etc. URL):

  • Put the desired output extension in the filename before .ts/.js.
  • Example: src/pages/data.json.ts → serves/builds at /data.json (not /data.json.ts). [1]

Examples

  • src/pages/api/hello.ts/api/hello [2]
  • src/pages/robots.txt.ts/robots.txt [1]
  • src/pages/sitemap.xml.ts/sitemap.xml [1]
  • Dynamic: src/pages/api/[id].json.ts/api/123.json (with getStaticPaths() in static output). [1]

Rule of thumb:
Use name.<target-ext>.ts when you want the URL to end in .<target-ext>, because only the final .ts/.js is removed. [1]

Sources: [1] Astro Endpoints docs, [2] Astro “Call endpoints from the server” recipe.


🏁 Script executed:

find . -name "*icon*" -type f | grep -E "(test|\.ts)" | head -20

Repository: patternfly/patternfly-doc-core

Length of output: 1963


🏁 Script executed:

cat -n "src/__tests__/pages/api/__tests__/[version]/icons/[iconName].test.ts"

Repository: patternfly/patternfly-doc-core

Length of output: 6288


🏁 Script executed:

cat -n "src/pages/api/[version]/icons/[iconSet].ts" | head -50

Repository: patternfly/patternfly-doc-core

Length of output: 1699


🏁 Script executed:

# Let's search for any fetch calls in the endpoint to see what URL pattern is actually being requested
grep -n "fetch" "src/pages/api/[version]/icons/[iconName].ts" -A 2 -B 2

Repository: patternfly/patternfly-doc-core

Length of output: 903


🏁 Script executed:

cat -n "src/utils/icons/fetch.ts"

Repository: patternfly/patternfly-doc-core

Length of output: 2165


SVG fetch URL regex is wrong — it requires a .json suffix that Astro never produces for [iconSet].ts.

Per Astro's file naming convention, [iconSet].ts (without .json in the filename) builds a /api/{version}/icons/{iconSet} endpoint with no .json suffix. To produce a .json endpoint, the file would need to be named [iconSet].json.ts. The prerendered route file is [iconSet].ts, so it serves at /api/{version}/icons/{iconSet}.

The regex /\/api\/[^/]+\/icons\/([^/]+)\.json/ will never match the URL fetchIconSvgs actually requests (/api/v6/icons/pf). The fallback branch returns mockApiIndex, so svgs?.['CircleIcon'] resolves to undefinedsvg = null → 404. The "returns SVG markup for valid icon" test is therefore verifying the wrong code path and will produce a false result.

🐛 Proposed fix: remove the stray `.json` from the regex
-    const match = url.match(/\/api\/[^/]+\/icons\/([^/]+)\.json/)
+    const match = url.match(/\/api\/[^/]+\/icons\/([^/]+)$/)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const match = url.match(/\/api\/[^/]+\/icons\/([^/]+)\.json/)
if (match) {
const setId = match[1]
const svgs = mockIconSvgs[setId] ?? {}
return Promise.resolve({
ok: true,
json: () => Promise.resolve(svgs),
} as Response)
const match = url.match(/\/api\/[^/]+\/icons\/([^/]+)$/)
if (match) {
const setId = match[1]
const svgs = mockIconSvgs[setId] ?? {}
return Promise.resolve({
ok: true,
json: () => Promise.resolve(svgs),
} as Response)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/pages/api/__tests__/`[version]/icons/[iconName].test.ts around
lines 31 - 38, The URL regex used to detect SVG fetches requires a trailing
".json" and therefore never matches the actual Astro endpoint; update the
url.match call that uses /\/api\/[^/]+\/icons\/([^/]+)\.json/ to remove the
"\.json" so it matches /api/{version}/icons/{iconSet} (e.g. use
/\/api\/[^/]+\/icons\/([^/]+)/), keeping the same capture group so setId and the
subsequent lookup into mockIconSvgs still work and the test exercises the
intended SVG branch instead of falling back to mockApiIndex.

Comment on lines +39 to +53
export async function fetchIconSvgs(
url: URL,
version: string,
setId: string,
): Promise<Record<string, string> | null> {
const iconsSvgsUrl = new URL(`/api/${version}/icons/${setId}`, url.origin)
const response = assetsFetch
? await assetsFetch(new Request(iconsSvgsUrl))
: await fetch(iconsSvgsUrl)

if (!response.ok) {
return null
}

return (await response.json()) as Record<string, string>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for assetsFetch declarations and usage
echo "=== Searching for assetsFetch in the codebase ==="
rg -n "assetsFetch" --type=ts -A2 -B2

echo ""
echo "=== Searching in .d.ts files ==="
fd -e "d.ts" -x grep -l "assetsFetch" {}

Repository: patternfly/patternfly-doc-core

Length of output: 1312


🏁 Script executed:

# Read the actual file to verify the function signature and JSDoc
echo "=== Reading src/utils/icons/fetch.ts ==="
wc -l src/utils/icons/fetch.ts

Repository: patternfly/patternfly-doc-core

Length of output: 142


🏁 Script executed:

# Read the file directly to see the exact function signature
sed -n '35,55p' src/utils/icons/fetch.ts

Repository: patternfly/patternfly-doc-core

Length of output: 717


🏁 Script executed:

# Check the call site to confirm assetsFetch is being passed
sed -n '45,55p' src/pages/api/[version]/icons/[iconName].ts

Repository: patternfly/patternfly-doc-core

Length of output: 330


Add missing assetsFetch parameter to function signature.

The function signature (lines 39-43) declares only three parameters, but the JSDoc at line 37 documents an optional @param assetsFetch parameter, and the function body uses assetsFetch at lines 45–46. Additionally, the call site at src/pages/api/[version]/icons/[iconName].ts:51 passes assetsFetch as a fourth argument, which is currently ignored.

This will cause either a TypeScript compile error or assetsFetch to be undefined at runtime. Restore the parameter to the function signature:

Proposed fix
 export async function fetchIconSvgs(
   url: URL,
   version: string,
   setId: string,
+  assetsFetch?: (request: Request) => Promise<Response>,
 ): Promise<Record<string, string> | null> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/icons/fetch.ts` around lines 39 - 53, The function fetchIconSvgs is
missing the optional assetsFetch parameter used in its body; update its
signature to accept a fourth optional parameter (e.g., assetsFetch?: (req:
Request) => Promise<Response>) so the internal conditional (assetsFetch ? await
assetsFetch(new Request(...)) : await fetch(...)) uses the passed-in function,
and ensure callers like the one in src/pages/api/[version]/icons/[iconName].ts
continue passing assetsFetch; keep the return behavior unchanged.

"$schema": "node_modules/wrangler/config-schema.json",
"name": "patternfly-docs-core",
"compatibility_date": "2025-06-17",
"compatibility_date": "2026-02-18",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Cloudflare Workers compatibility_date breaking changes 2025 2026

💡 Result:

Cloudflare Workers’ compatibility_date gates backwards-incompatible runtime behavior changes (implemented as “compatibility flags”). Updating the date opts your Worker into all flag defaults up to that date; leaving it unchanged preserves old behavior indefinitely. [1][2]

Compatibility-date “breaking change” defaults that landed in 2025 (most relevant)

From Cloudflare’s Compatibility flags documentation, these flags became default-on at the listed dates (meaning your Worker’s behavior can change when you set compatibility_date on/after them): [3]

  • 2025-04-01: Node.js compat can populate process.env from bindings by default (under nodejs_compat). [3][4]
  • 2025-04-02: request.cf cache settings for Fetch can override Cache Rules by default (request_cf_overrides_cache_rules). [3][5]
  • 2025-05-19: navigator.language enabled by default (enable_navigator_language). [3]
  • 2025-06-01: Allow eval() / new Function() during startup by default (allow_eval_during_startup). [3]
  • 2025-06-16: AsyncLocalStorage snapshots bound to request by default (bind_asynclocalstorage_snapshot_to_request). [3]
  • 2025-06-16: Unrecognized import assertions throw by default (throw_on_unrecognized_import_assertion). [3]
  • 2025-08-01: EventTarget handlers get spec-compliant this by default (set_event_target_this). [3]
  • 2025-08-07: Request.cache = "no-cache" becomes valid by default (cache_no_cache_enabled). [3]
  • 2025-08-14: Python Workers disable “global handlers” by default (python_no_global_handlers). [3]
  • 2025-08-15: Expose MessageChannel/MessagePort globally by default (expose_global_message_channel). [3]
  • 2025-08-15: Enable Node client modules node:http/node:https by default (when using nodejs_compat) (enable_nodejs_http_modules). [3]
  • 2025-09-01: Enable Node http.server-style modules by default (when using nodejs_compat). [3]

Cache Rules interaction (extra 2025 “gotcha”)

Cloudflare separately documents that overriding Cache Rules also depends on additional cache-related compat flags becoming default-on at specific 2025 dates (notably cache_api_compat_flags and cache_api_request_cf_overrides_cache_rules). [5]

What changed in 2026?

As of Cloudflare’s Workers docs through January 29, 2026, the Workers platform changelog shows V8 version updates in January 2026, but these are described as platform updates (not compatibility_date-gated breaking changes). [6]

Practical takeaway: for “breaking changes”, review the Compatibility flags page when moving your compatibility_date across the 2025 dates above; for 2026 so far, watch the Workers platform changelog for runtime upgrades. [3][6]

Sources: Compatibility dates [1], backwards-compatibility explanation [2], compatibility flags (defaults by date) [3], env vars / process.env details [4], Workers + Cache Rules interaction [5], Workers platform changelog (2026 entries) [6].


Review and test for breaking changes across the ~8-month compatibility window jump before deploying.

compatibility_date moved from 2025-06-17 to 2026-02-18, crossing multiple documented breaking changes. With nodejs_compat enabled, the most critical are:

  • 2025-08-15: Node.js http/https modules enabled by default; MessageChannel/MessagePort exposed globally
  • 2025-09-01: Node.js http.server-style modules enabled by default
  • 2025-08-01: EventTarget handlers receive spec-compliant this
  • 2025-08-07: cache_no_cache_enabled by default

If existing code depends on pre-August 2025 behavior (e.g., legacy Node.js http handling, cache semantics, event handler context), it will break silently at runtime after deployment. Verify your Worker's runtime behavior against these compatibility flags before merging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wrangler.jsonc` at line 4, compatibility_date was bumped from 2025-06-17 to
2026-02-18 (the "compatibility_date" key) which crosses several Node.js/runtime
breaking changes when "nodejs_compat" is enabled; before merging, run
integration and smoke tests exercising HTTP handlers, MessageChannel/MessagePort
usage, EventTarget-bound handlers (verify correct `this`), and caching behavior
(cache_no_cache_enabled) against a worker runtime with the new date, and if any
regressions appear either revert the compatibility_date, explicitly set/lock
"nodejs_compat" feature flags to the expected pre-2025 behavior, or update the
worker code paths to follow the post-2025 semantics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments